from typing import Optional, Tuple, Dict
import time as lib_time

from django.db import transaction
from django.db.models import QuerySet, Q

from core.loggers import config_script_logger
from apps.app_tx_meeting.models import MeetingReservation, MeetingReservationLog
from apps.app_tx_meeting.helpers import MeetReservationHelper


class BaseTxRoomHelper:
    @staticmethod
    def tx_release_room_1(tx_meeting_userid: str, room_type: int) -> Tuple[bool, str]:
        raise NotImplementedError('tx_release_room_1')

    @staticmethod
    def tx_reserve_room_1(tx_meeting_userid: str, room_type: int) -> Tuple[bool, str]:
        raise NotImplementedError('tx_reserve_room_1')


class RoomReserveReleaseWorker:
    """
    会议室资源预留和释放

    要先释放会议室资源，后 才有资源可分配预留
    """
    @staticmethod
    def get_now_ts() -> int:
        return int(lib_time.time())

    def __init__(self, log: bool = True, tx_room_helper: Optional[BaseTxRoomHelper] = None):
        if log:
            self.logger = config_script_logger(
                name='timer_tx_meet_room_reserve_logger', filename='timer_tx_meet_room_reserve.log', stdout=False)
        else:
            self.logger = None

        self.tx_room_helper = tx_room_helper if tx_room_helper else MeetReservationHelper()

    def add_log(
            self, operate_status: str, content: str, reservation_id: str
    ):
        log_type = MeetingReservationLog.LogType.ROOM.value
        try:
            MeetingReservationLog.add_log(
                log_type=log_type, operate_status=operate_status, content=content, reservation_id=reservation_id
            )
        except Exception as e:
            pass

        try:
            if self.logger:
                self.logger.info(f'[{log_type}], {operate_status},预定记录id（{reservation_id}）,{content}')
        except Exception as e:
            pass

    def run(self):
        # 释放和预留的时间点统一，释放资源会耗时一段时间，这段时间内可能又有满足释放条件的记录，这些资源不释放，接下来的预留资源可能不足
        # 时间点也可以不统一，预留资源可能不足，预留失败，下次释放后 再预留
        now_ts = self.get_now_ts()
        # 要先释放会议室资源，后 才有资源可分配预留
        release_ret = self.do_release_room_of_meetings(now_ts=now_ts)
        reserve_ret = self.do_reserve_room_for_meetings(now_ts=now_ts)
        return {
            'release': release_ret,
            'reserve': reserve_ret
        }

    def do_release_room_of_meetings(self, now_ts: int) -> Dict[str, int]:
        ok_count = 0
        error_count = 0
        nothing_count = 0

        reservations = self.get_need_release_reservations(now_ts=now_ts)
        for reservation in reservations:
            ret = self.release_meeting_room(reservation)
            if ret is None:
                nothing_count += 1
            elif ret:
                ok_count += 1
            else:
                error_count += 1

        return {'ok_count': ok_count, 'error_count': error_count, 'nothing_count': nothing_count}

    def do_reserve_room_for_meetings(self, now_ts: int) -> Dict[str, int]:
        ok_count = 0
        error_count = 0
        nothing_count = 0

        reservations = self.get_need_reserve_reservations(now_ts=now_ts)
        for reservation in reservations:
            ret = self.reserve_meeting_room(reservation)
            if ret is None:
                nothing_count += 1
            elif ret:
                ok_count += 1
            else:
                error_count += 1

        return {'ok_count': ok_count, 'error_count': error_count, 'nothing_count': nothing_count}

    @staticmethod
    def get_need_release_reservations(now_ts: int) -> QuerySet:
        """
        需要释放会议室资源的 会议预定记录

        已过预定结束时间，room预留截止时间前5分钟之后
        """
        qs = MeetingReservation.objects.filter(
            Q(
                room_reserve_status__in=[
                    MeetingReservation.RoomReserveStatus.RESERVED.value,    # 已预留会议室资源的
                    MeetingReservation.RoomReserveStatus.UNRELEASED.value,  # 议室资源释放失败的
                ]
            ),
            # “时间”，或者 “预定状态” 满足释放条件
            Q(
                end_time__lte=now_ts,    # 已过预定结束时间
                room_reserve_end__lte=now_ts + 300,   # 5分钟后就过了room预留截止时间的
            ) |
            Q(
                status__in=[
                    MeetingReservation.Status.CANCELLED.value,
                    MeetingReservation.Status.NO_SHOW.value
                ]
            )
        )
        # 预留截止时间小的先释放
        return qs.order_by('room_reserve_end')

    @staticmethod
    def get_need_reserve_reservations(now_ts: int) -> QuerySet:
        """
        需要预留会议室资源的 会议预定记录

        已过room预留开始时间，会议预定结束时间前5分钟之前
        """
        qs = MeetingReservation.objects.filter(
            status=MeetingReservation.RoomReserveStatus.RESERVED.value,
            room_reserve_status__in=[
                MeetingReservation.RoomReserveStatus.WAIT.value,  # 待预留会议室资源的
                MeetingReservation.RoomReserveStatus.UNRESERVED.value,  # 预留议室资源失败的
            ],
            room_reserve_start__lte=now_ts,    # 已过room预留开始时间
            end_time__gt=now_ts + 60 * 5,   # 会议预定结束时间前5分钟之前
        )
        # 预留开始时间小的先预留
        return qs.order_by('room_reserve_start')

    def release_meeting_room(
            self, reservation: MeetingReservation, early_release: bool = False, log_msg: str = ''
    ) -> Optional[bool]:
        """
        释放 会议预定 的会议室资源

        :param reservation: 会议预定记录对象
        :param early_release: True(提前释放)
        :param log_msg: 添加到日志记录的额外内容
        :return:
            None    # do nothing
            True    # 正常，资源释放成功，或者失败，资源释放结果状态更新到预定记录中
            False  # 发生错误
        """

        if not self._is_need_release_room(reservation=reservation, early_release=early_release):
            return None

        is_release_ok = False
        release_desc = ''
        try:
            with transaction.atomic():
                reservation = MeetingReservation.objects.select_for_update().filter(id=reservation.id).first()
                if not self._is_need_release_room(reservation=reservation, early_release=early_release):
                    return None

                # 释放资源
                is_release_ok, release_desc = self.tx_room_helper.tx_release_room_1(
                    tx_meeting_userid=reservation.tx_meeting_userid, room_type=reservation.room_type
                )
                if is_release_ok:
                    room_reserve_status = MeetingReservation.RoomReserveStatus.RELEASED.value
                    operate_status = MeetingReservationLog.OperateStatus.OK.value
                else:
                    room_reserve_status = MeetingReservation.RoomReserveStatus.UNRELEASED.value
                    operate_status = MeetingReservationLog.OperateStatus.FAIL.value

                self.update_reservation_room_status(
                    reservation=reservation, room_reserve_status=room_reserve_status, room_reserve_desc=release_desc)

            if early_release:
                release_desc = '提前释放资源；' + release_desc

            self.add_log(
                operate_status=operate_status, reservation_id=reservation.id, content=release_desc + log_msg
            )
            return True
        except Exception as exc:
            if is_release_ok:
                content = release_desc + f'。会议室资源已释放，预定记录会议室资源状态更新失败，{str(exc)}'
            else:
                content = f'释放会议室资源时错误，{str(exc)}。{release_desc}'

            self.add_log(
                operate_status=MeetingReservationLog.OperateStatus.NONE.value,
                reservation_id=reservation.id,
                content=content
            )

            return False

    def reserve_meeting_room(self, reservation: MeetingReservation) -> Optional[bool]:
        """
        预留 会议预定 的会议室资源

        :return:
            None    # do nothing
            True    # 正常，资源释放成功，或者失败，资源释放结果状态更新到预定记录中
            False  # 发生错误
        """

        if not self._is_need_reserve_room(reservation=reservation):
            return None

        is_reserve_ok = False
        reserve_desc = ''
        try:
            with transaction.atomic():
                reservation = MeetingReservation.objects.select_for_update().filter(id=reservation.id).first()
                if not self._is_need_reserve_room(reservation=reservation):
                    return None

                # 预留资源
                is_reserve_ok, reserve_desc = self.tx_room_helper.tx_reserve_room_1(
                    tx_meeting_userid=reservation.tx_meeting_userid, room_type=reservation.room_type
                )
                if is_reserve_ok:
                    room_reserve_status = MeetingReservation.RoomReserveStatus.RESERVED.value
                    operate_status = MeetingReservationLog.OperateStatus.OK.value
                else:
                    room_reserve_status = MeetingReservation.RoomReserveStatus.UNRESERVED.value
                    operate_status = MeetingReservationLog.OperateStatus.FAIL.value

                self.update_reservation_room_status(
                    reservation=reservation, room_reserve_status=room_reserve_status, room_reserve_desc=reserve_desc)

            self.add_log(
                operate_status=operate_status, reservation_id=reservation.id, content=reserve_desc
            )
            return True
        except Exception as exc:
            if is_reserve_ok:
                content = reserve_desc + f'。会议室资源已预留，但预定记录会议室资源状态更新失败，{str(exc)}'
            else:
                content = f'预留会议室资源时错误，{str(exc)}。{reserve_desc}'

            self.add_log(
                operate_status=MeetingReservationLog.OperateStatus.NONE.value,
                reservation_id=reservation.id,
                content=content
            )

            return False

    @staticmethod
    def update_reservation_room_status(
            reservation: MeetingReservation, room_reserve_status: str, room_reserve_desc: str
    ):
        """
        会议预定记录会议室资源预留状态更新

        :raise: Exception
        """
        reservation.room_reserve_status = room_reserve_status
        reservation.room_reserve_desc = room_reserve_desc[0:254]
        try:
            reservation.save(update_fields=['room_reserve_status', 'room_reserve_desc', 'update_time'])
        except Exception as e:
            reservation.save(update_fields=['room_reserve_status', 'room_reserve_desc', 'update_time'])

    @staticmethod
    def _is_need_release_room(reservation: MeetingReservation, early_release: bool = False) -> bool:
        """
        是否需要释放会议室资源

        :early_release: True(提前释放)
        """
        # “已预定”状态
        if reservation.status == MeetingReservation.Status.RESERVED.value:
            # 不是 “资源未释放” 状态
            if reservation.room_reserve_status not in [
                    MeetingReservation.RoomReserveStatus.RESERVED.value,    # 已预留会议室资源的
                    MeetingReservation.RoomReserveStatus.UNRELEASED.value,  # 议室资源释放失败的
                ]:
                return False

            now_ts = RoomReserveReleaseWorker.get_now_ts()
            # 如果是提前释放，当前时间需要在预定开始时间之后
            if early_release:
                if now_ts > reservation.start_time:
                    return True
                else:
                    return False

            # 正常时间释放，要到“预定结束时间”
            if now_ts >= reservation.end_time:
                return True

            return False
        # “已取消”、“违约”状态
        elif reservation.status in [MeetingReservation.Status.CANCELLED.value, MeetingReservation.Status.NO_SHOW.value]:
            # “资源未释放” 状态
            if reservation.room_reserve_status in [
                MeetingReservation.RoomReserveStatus.RESERVED.value,  # 已预留会议室资源的
                MeetingReservation.RoomReserveStatus.UNRELEASED.value,  # 议室资源释放失败的
            ]:
                return True

            return False

        # 其他，“资源未释放” 状态
        if reservation.room_reserve_status in [
            MeetingReservation.RoomReserveStatus.RESERVED.value,  # 已预留会议室资源的
            MeetingReservation.RoomReserveStatus.UNRELEASED.value,  # 议室资源释放失败的
        ]:
            return True

        return False

    @staticmethod
    def _is_need_reserve_room(reservation: MeetingReservation) -> bool:
        """
        是否需要预留会议室资源
        """
        # 不是“已预定”状态
        if reservation.status != MeetingReservation.Status.RESERVED.value:
            return False

        # 不是 “资源未预留” 状态
        if reservation.room_reserve_status not in [
            MeetingReservation.RoomReserveStatus.WAIT.value,  # 待预留会议室资源的
            MeetingReservation.RoomReserveStatus.UNRESERVED.value,  # 预留议室资源失败的
        ]:
            return False

        now_ts = RoomReserveReleaseWorker.get_now_ts()
        # 未到 “会议室预留开始时间”
        if reservation.room_reserve_start > now_ts:
            return False

        # 已过 “会议预定结束时间”
        if reservation.end_time <= now_ts:
            return False

        return True
